package com.feature50.makeover.utils;

import org.apache.log4j.Logger;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;

public class RandomUtils {
    private static final Logger logger = Logger.getLogger(RandomUtils.class);

    private static String randomWordsSeed = "the quick brown fox jumped over the lazy dog and made for the fence on the other " +
            "side of his property where he hoped he would find some good food and eat it for a long time " +
            "Amid higher gasoline prices and other effects of plans to cut her toy spending in half " +
            "You look at the economic climate overall and this is not a good time to run up the credit cards said " +
            "of East who will spend about on toys for each child, down from last year " +
            "the human suffering from has also made re-evaluate her budget for toys " +
            "We want to be more about the experience of giving and less about the getting she said";
    private static String[] randomWords = randomWordsSeed.toLowerCase().split(" ");

    private static Random random = new Random();

    public static String getRandomString(int length) {
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 1; i <= length; i++) {
            int charcode = random.nextInt(26);
            sb.append(String.valueOf((char) (charcode + 97)));
        }

        return sb.toString();
    }

    public static String getRandomWords(int words) {
        StringBuffer phrase = new StringBuffer();
        for (int i = 0; i < words; i++) {
            if (phrase.length() > 0) phrase.append(" ");
            phrase.append(randomWords[random.nextInt(randomWords.length)]);
        }
        return phrase.toString();
    }

    public static String getRandomWords() {
        return getRandomWords(numRandomWords());
    }

    private static int numRandomWords() {
        if (numRandomWords < 0) {
            return random.nextInt(2);
        } else {
            return numRandomWords;
        }
    }
    private static int numRandomWords = -1;
    public static void setNumRandomWords(int value) {
        numRandomWords = value;
    }

    public static long getRandomLong() {
        return random.nextLong();
    }

    public static int getRandomInt(int smallest, int largest) {
        int range = largest - smallest;
        int v = random.nextInt(range + 1);
        return smallest + v;
    }

    public static Date getRandomDate() {
        int year = getRandomInt(1970, 2004);
        int month = getRandomInt(1, 12);
        int day = getRandomInt(1, 28);

        Calendar cal = Calendar.getInstance();
        cal.set(year, month, day);
        return cal.getTime();
    }

    public static float getRandomFloat() {
        return random.nextFloat();
    }

    public static boolean getRandomBoolean() {
        return (random.nextInt() % 2) == 0;
    }

    /**
     * Generates random data for each object in the array.
     *
     * @param instances
     * @see #generateRandomData(Object)
     */
    public static Object[] generateRandomData(Object[] instances) {
        for (int i = 0; i < instances.length; i++) {
            Object instance = instances[i];
            if (instance == null) {
                try {
                    instance = instances.getClass().getComponentType().newInstance();
                    instances[i] = instance;
                } catch (Exception e) {
                    logger.error("Couldn't generate new instance when passed null array entry", e);
                }
            }
            if (instance != null) generateRandomData(instance);
        }
        return instances;
    }

    /**
     * Finds all the setters on a bean and invokes them, sending in random data based on the type of the argument.
     * Unfortunately, the logic for generating the random data based on the type is hard-coded and rather simplistic
     * at the moment. This method is quite handy for rapid prototyping.
     *
     * @param instance
     * @see #generateRandomData(Object[])
     */
    public static Object generateRandomData(Object instance) {
        if (instance.getClass().isArray()) {
            generateRandomData((Object[]) instance);
            return instance;
        }

        Method[] methods = ReflectionUtils.getSetterMethods(instance);
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            Class[] types = method.getParameterTypes();
            if ((types == null) || (types.length != 1)) continue;
            Class type = types[0];
            try {
                generateRandomDataForSetter(method, type, instance);
            } catch (Exception e) {
                logger.warn("Couldn't generate random data", e);
            }
        }

        return instance;
    }

    private static void generateRandomDataForSetter(Method method, Class type, Object instance) throws IllegalAccessException, InvocationTargetException {
        Object value = null;

        if (type.isArray()) {
            value = Array.newInstance(type.getComponentType(), getRandomInt(1, 10));
            Object[] array = (Object[]) value;
            try {
                for (int i = 0; i < array.length; i++) {
                    Object obj = type.getComponentType().newInstance();
                    Array.set(value, i, obj);
                    generateRandomData(obj);
                }
            } catch (InstantiationException e) {
                logger.warn("Couldn't create an instance of an array type", e);
            }
        } else if (type.equals(String.class)) {
            value = getRandomWords();
        } else if ((type.equals(int.class)) || (type.equals(Integer.class))) {
            value = new Integer(getRandomInt(0, 1000));
        } else if (type.equals(Date.class)) {
            value = getRandomDate();
        } else if ((type.equals(long.class)) || (type.equals(Long.class))) {
            value = new Long(getRandomLong());
        } else if ((type.equals(float.class)) || (type.equals(Float.class))) {
            value = new Float(getRandomFloat());
        } else if ((type.equals(double.class)) || (type.equals(Double.class))) {
            value = new Float(getRandomFloat());
        } else if ((type.equals(boolean.class)) || (type.equals(Boolean.class))) {
            value = new Boolean(getRandomBoolean());
        }

        if (value != null) method.invoke(instance, new Object[] { value });
    }
}